// Copyright (C) ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
//
// $Id: schedule.cpp 225 2008-09-12 17:43:02Z heavyzheng $
//

#ifndef ZZHENG_QUANT_DATES_SOURCE
#define ZZHENG_QUANT_DATES_SOURCE
#endif // ZZHENG_QUANT_DATES_SOURCE

#include <zzheng/quant/dates/schedule.hpp>
#include <zzheng/quant/dates/exception.hpp>
#include <zzheng/quant/dates/function.hpp>

#include <cassert>
#include <deque>
#include <algorithm>
#include <string>

namespace zzheng {
namespace quant {
namespace dates {

    namespace {

        //! Typedef temporary schedule type. Depending on the stub type, we may need to generate
        //! schedule from start date to end date or from end date to start date, so we use
        //! std::deque as the temporary schedule container, which allows fast access to both the
        //! front element and the end element.
        typedef std::deque<adjusted_period> temp_schedule;

        //! Generates schedule forward or backward.
        void generate_schedule(bool forward,
                               const date& from_date,
                               const date& to_date,
                               const duration& freq,
                               bool end_of_month,
                               stub_t stub,
                               rolling_t rolling,
                               const calendar& cal,
                               temp_schedule& results) {

            int count = 1;
            for (date d1 = from_date; d1 != to_date; ) {

                // Calculate the current period dates (d1 and d2), and check if it is irregular.
                bool irregular = false;
                date d2 = add_duration(from_date, freq * count, end_of_month);
                if ( (forward && d2 > to_date) || (!forward && d2 < to_date) ) {
                    irregular = true;
                    d2 = to_date;
                }

                // Create an adjusted date pair for the current period.
                adjusted_date adj_d1(adjust(d1, rolling, cal), d1);
                adjusted_date adj_d2(adjust(d2, rolling, cal), d2);
                adjusted_period curr_period = std::make_pair( (std::min)(adj_d1, adj_d2),
                                                              (std::max)(adj_d1, adj_d2) );

                // If the current period is irregular (is short period):
                // - if no stub is allowed, throw exception.
                // - if stub is long, merge (if possible) with the previous period.
                // - otherwise, do nothing.
                if (irregular) {
                    if (stub == no_stub) {
                        throw date_error("Irregular period found but no stub is allowed.");
                    } else if (is_long(stub) && !results.empty()) {
                        adjusted_period prev_period = results.back();
                        results.pop_back();
                        curr_period = ( forward
                                      ? std::make_pair(prev_period.first, curr_period.second)
                                      : std::make_pair(curr_period.first, prev_period.second) );
                    }
                }

                // If the current period is valid, push it to the back of the results.
                if (curr_period.first.adjusted() < curr_period.second.adjusted()) {
                    results.push_back(curr_period);
                }

                // Update the iterating variables.
                d1 = d2;
                ++count;
            }
        }


        #ifdef ZZHENG_DEBUG

        //! Asserts the generated schedule are valid. This function is enabled only in debug
        //! version. When the library is compiled in release, this function just does nothing.
        void assert_valid_schedule(const schedule& results) {
            date prev_adj_end_date;
            for (schedule::const_iterator i = results.begin(); i != results.end(); ++i) {
                assert(i->first.adjusted() < i->second.adjusted());
                if (!prev_adj_end_date.empty()) {
                    assert(i->first.adjusted() == prev_adj_end_date);
                }
                prev_adj_end_date = i->second.adjusted();
           }
        }

        #else

        void assert_valid_schedule(const schedule&) {
            // Do nothing in release variant.
        }

        #endif // ZZHENG_DEBUG

    } // anonymous namespace


    ////////////////////////////////////////////////////////////////////////////////////////////////

    void generate_schedule(const date& start_date,
                           const date& end_date,
                           const duration& freq,
                           bool end_of_month,
                           stub_t stub,
                           rolling_t rolling,
                           const calendar& cal,
                           schedule& results) {

        // Check parameters.
        if (start_date >= end_date) {
            std::string errmsg = "Invalid start date " + start_date.str()
                               + " and end date " + end_date.str() + ".";
            throw date_error(errmsg.c_str());
        }
        if (freq.count() <= 0) {
            std::string errmsg = "Invalid frequency " + freq.str() + ".";
            throw date_error(errmsg.c_str());
        }

        // Generate schedule to a temporary container.
        temp_schedule temp;
        if (stub == no_stub || is_last(stub)) {
            generate_schedule(true,       // generate forward...
                              start_date, // from start date...
                              end_date,   // to end date...
                              freq,       // using positive frequency.
                              end_of_month,
                              stub,
                              rolling,
                              cal,
                              temp);
        } else {
            generate_schedule(false,      // generate backward...
                              end_date,   // from end date...
                              start_date, // to start date...
                              -freq,      // using negative frequency.
                              end_of_month,
                              stub,
                              rolling,
                              cal,
                              temp);
            std::reverse(temp.begin(), temp.end());
        }

        // Assign the temporary container to the result schedule.
        results.assign(temp.begin(), temp.end());
        assert_valid_schedule(results);
    }


    void generate_schedule(const date& start_date,
                           const duration& maturity,
                           const duration& freq,
                           bool end_of_month,
                           stub_t stub,
                           rolling_t rolling,
                           const calendar& cal,
                           schedule& results) {
        date end_date = add_duration(start_date, maturity, end_of_month);
        generate_schedule(start_date, end_date, freq, end_of_month, stub, rolling, cal, results);
    }

} // namespace zzheng::quant::dates
} // namespace zzheng::quant
} // namespace zzheng



